#!/usr/bin/env python

#   
#     newsrc.py - A Python module to read and write newsrc files
#
#  Copyright (c) 2007, Alex Marandon
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#  
#    * Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#    * Neither the name of newsrc.py nor the names of its contributors
#      may be used to endorse or promote products derived from this software 
#      without specific prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.

import re, string, os

class NewsGroup:
    def __init__(self, name):
        self.name = name
        self.subscribed = ':'
        self.articles = Articles()

class Newsrc:
    """
    A newsrc file.
    """

    def __init__(self, file = ''):
        """
        Constructor takes a newsrc file as argument.
        """

        self.groups  = [ ]
        self.file = file
        if file != '' and os.path.isfile(file):
            self.load(file)

    def __str__(self):
        s = ''
        for line in self.export_rc():
            s += line + "\n"
        return s

    def load(self, file):

        self.file = file

        newsrc_fh = open(file, 'r')
        lines = newsrc_fh.readlines()
        newsrc_fh.close()

        self.import_rc(lines)
        
    def import_rc(self, lines):
        for line in lines:
            group = self._parseLine(line)
            if group:
                self.groups.append(group)

    def export_rc(self):
        result = []
        for group in self.groups:
            line = group.name + group.subscribed + ' ' \
                + group.articles.getString()
            result.append(line)
        return result

    def save(self):
        dest_file = ''
        if self.file == '':
            dest_file = 'newsrc'
        else:
            dest_file = self.file
        self.saveAs(dest_file)
            
    def saveAs(self, file):
        if os.path.exists(file):
            os.rename(file, file+'.bak')
        fh = open(file, 'w')
        for line in self.export_rc():
            fh.write(line + '\n')

    def addGroup(self, group_name, replace = False):
        new_group = NewsGroup(group_name)
        if self.hasGroup(group_name):
            if replace:
                self.delGroup(group_name)
                self.groups.append(new_group)
                return True
            else:
                return False
        else:
            self.groups.append(new_group)
            return True

    def hasGroup(self, group_name):
        for group in self.groups:
            if group.name == group_name:
                return True
        return False

    def delGroup(self, group_name):
        for group in self.groups:
            if group.name == group_name:
                self.groups.remove(group)
                return True
        return False

    def isSubscribed(self, group_name):
        group = self._getGroupByName(group_name)
        if not group:
            return False
        elif group.subscribed == '!':
            return False
        else:
            return True

    def subscribe(self, group_name):
        self._changeSubscription(group_name, ':')

    def unsubscribe(self, group_name):
        self._changeSubscription(group_name, '!')

    def isMarked(self, group_name, article_nb):
        group = self._getGroupByName(group_name)
        if group:
            return group.articles.marked(article_nb)
        else:
            return False

    def mark(self, group_name, article_nb):
        self.markList(group_name, (article_nb,))

    def markRange(self, group_name, start, end):
        self.markList(group_name, range(start, end + 1))

    def markList(self, group_name, article_nb_list):
        group = self._getGroupByName(group_name)
        group.articles.add(article_nb_list)

    def unMark(self, group_name, article_nb):
        return self.unMarkList(group_name, (article_nb,))
        
    def unMarkRange(self, group_name, start, end):
        self.unMarkList(group_name, range(start, end + 1))

    def unMarkList(self, group_name, article_nb_list):
        group = self._getGroupByName(group_name)
        return group.articles.remove(article_nb_list)

    def _changeSubscription(self, group_name, char):
        if self.hasGroup(group_name):
            group = self._getGroupByName(group_name)
            group.subscribed = char
        else:
            self.addGroup(group_name)

    def _getGroupByName(self, group_name):
        for group in self.groups:
            if group.name == group_name:
                return group
        return False

    def _parseLine(self, line): 
        """
        Parses a single line from a newsrc file
        """

        if line.isspace(): return False

        line = re.compile('\s').sub('', line) + '\n'

        m = re.compile('([^!:]+)([!:])(.*)').match(line)
        (name, mark, articles_string) = (m.group(1), m.group(2), m.group(3))
        articles = Articles(articles_string)
        group = NewsGroup(name)
        group.subscribed = mark
        group.articles = articles
        return group

class Articles:
    """
    A list of articles.
    """

    def __init__(self, articles_string = ''):

        # String representing a list of articles (newsrc format)
        self.articles_string = articles_string

        # List of articles numbers.
        self.articles = [ ]

        if articles_string == '':
            return

        parts = articles_string.split(',')

        # Filling article numbers list
        for part in parts:
            if part.find('-') < 0:
                self.articles.append(int(part))
            else:
                (first, last) = part.split('-')
                first, last = int(first), int(last)
                while first <= last:
                    self.articles.append(first)
                    first += 1

        self.articles.sort()
        
    def getList(self):
        return self.articles

    def add(self, list):
        for a in list:
            if not a in self.articles:
                self.articles.append(a)
        self.articles.sort()

    def remove(self, list):
        for i in list: 
            if i not in self.articles:
                return False
            else:
                self.articles.remove(i)
                self.articles.sort()
        return True

    def marked(self, article_nb):
        if article_nb in self.articles:
            return True
        else:
            return False

    def getString(self):
        self.articles_string = ''
        in_range = 0
        for article in self.articles:
            current_index = self.articles.index(article)
            if current_index + 1 == len(self.articles): 
                # Last element in list
                self.articles_string += str(article)
                break
            next_article  = self.articles[current_index + 1]
            if next_article - article == 1:
                # We're inside a range
                if not in_range:
                    # 1st element of list
                    self.articles_string += str(article) + '-'
                in_range = 1 
            elif next_article - article > 1:
                # Last element of range (or unique element).
                in_range = 0
                self.articles_string += str(article) + ','

        return self.articles_string

#EOF
